🎓 Maestría en Inteligencia Artificial Aplicada¶

Institution Course Activity


🖼️ Visión Computacional para Imágenes y Video¶

👨‍🏫 Profesores¶

  • Profesor Titular: Dr. Gilberto Ochoa Ruiz
  • Profesor Asistente: MIP Ma. del Refugio Melendez Alfaro
  • Profesor Tutor: M. en C. Jose Angel Martinez Navarro

Actividad 7.2: Algoritmos de Extracción de Características - Feature Matching¶

📌 Detalles de la Actividad¶

  • Código: 7.2 Google Colab
  • Título: Extracción de características y Feature Matching
  • Fecha de entrega: 📅 Octubre 26, 2025 a las 23:59
  • Formato de entrega: ZIP
  • Modalidad: Equipo

👥 Team 13¶

🚀 Nuestro Equipo¶

Javier Augusto Rebull Saucedo¶

Javier Augusto Rebull Saucedo

Matrícula: A01795838
🎓 MNA Student

Juan Carlos Pérez Nava¶

Juan Carlos Pérez Nava

Matrícula: A01795941
🎓 MNA Student

Luis Gerardo Sánchez Salazar¶

Luis Gerardo Sánchez Salazar

Matrícula: A01232963
🎓 MNA Student

Oscar Enrique García García¶

Oscar Enrique García García

Matrícula: A01016093
🎓 MNA Student


Objetivo del Proyecto¶

Los algoritmos de extracción de características son fundamentales en visión computacional moderna, siendo componentes esenciales para:

  • Detección y matching de puntos de interés entre imágenes
  • Creación de panoramas mediante image stitching
  • Rastreo de objetos en robótica y sistemas autónomos
  • Reconstrucción 3D y Structure from Motion (SfM)
  • Reconocimiento visual y navegación de drones
  • Realidad aumentada y tracking en tiempo real

En esta actividad implementaremos y compararemos tres algoritmos fundamentales de feature detection y matching:

🔹 Harris Corner Detector - Detección de esquinas basada en cambios de intensidad
🔹 SIFT (Scale Invariant Feature Transform) - Descriptor robusto invariante a escala y rotación
🔹 ORB (Oriented FAST and Rotated BRIEF) - Alternativa eficiente y libre de patentes

Evaluaremos el desempeño de estos métodos bajo diferentes condiciones: cambios de iluminación, rotación, escala y oclusión parcial. Aplicaremos principios de Clean Code para generar código mantenible, escalable y profesional.


📚 Tabla de Contenidos¶

  1. Configuración Inicial e Importación de Librerías
  2. Descarga y Preparación de Imágenes
    • 2.1 Descarga automática con gdown
    • 2.2 Funciones de utilidad (resize, conversión a escala de grises)
  3. ORB (Oriented FAST and Rotated BRIEF)
    • 3.1 Introducción a ORB
    • 3.2 Carga de imagen demo
  4. Creación de Imagen de Prueba con Invarianza
    • 4.1 Transformaciones de escala (pirámide gaussiana)
    • 4.2 Transformaciones de rotación
  5. Visualización de Imágenes de Entrenamiento y Prueba
  6. Detección de Keypoints con ORB
    • 6.1 Extracción de keypoints y descriptores
    • 6.2 Visualización con y sin tamaño
    • 6.3 Matching con Brute Force Matcher
  7. SIFT Matching (Scale Invariant Feature Transform)
    • 7.1 Introducción a SIFT
    • 7.2 Detección de keypoints en imagen única
  8. Matching entre Diferentes Imágenes
    • 8.1 Carga de par de imágenes (Elon Musk)
  9. Extracción de Keypoints con SIFT
    • 9.1 Detección en ambas imágenes
  10. Feature Matching Comparativo
    • 10.1 Matching con norma L2
    • 10.2 Visualización de top matches
  11. Análisis Comparativo de Algoritmos
    • 11.1 Framework de comparación (Clase FeatureMatchingComparator)
    • 11.2 Harris Corner Detection + ORB Descriptors
    • 11.3 Comparación Demo: Elon Musk
    • 11.4 Comparaciones Wall-E (múltiples escenarios)
    • 11.5 Comparación Puzzle de Popeye
    • 11.6 Visualización de resultados agregados
    • 11.7 Tabla comparativa detallada
    • 11.8 Estadísticas agregadas
    • 11.9 Hallazgos y análisis de resultados
  12. Conclusiones Personales
    • 12.1 Javier Augusto Rebull Saucedo
    • 12.2 Juan Carlos Pérez Nava
    • 12.3 Luis Gerardo Sánchez Salazar
    • 12.4 Oscar Enrique García García
  13. Reflexión Final del Equipo
    • 13.1 Logros destacados
    • 13.2 Aprendizajes clave
    • 13.3 Aplicaciones potenciales
    • 13.4 Referencias bibliográficas

🔬 Metodología de Análisis¶

Para garantizar una evaluación rigurosa y científica, seguiremos esta metodología:

  1. Detección de Keypoints: Identificar puntos distintivos en cada imagen
  2. Computación de Descriptores: Generar vectores de características para cada keypoint
  3. Matching: Encontrar correspondencias entre descriptores de dos imágenes
  4. Evaluación Cuantitativa: Métricas de distancia promedio, número de matches
  5. Evaluación Cualitativa: Análisis visual de la calidad de los matches
  6. Comparación Multi-Escenario: Probar bajo rotaciones, escalas y oclusiones

💻 Tecnologías Utilizadas¶

  • Python 3.8+ - Lenguaje de programación
  • OpenCV 4.x - Biblioteca de visión computacional
  • NumPy - Computación numérica
  • Matplotlib / Seaborn - Visualización de datos
  • Google Colab - Entorno de ejecución
  • gdown - Descarga de datasets desde Google Drive

🚀 ¡Comencemos con el análisis comparativo de feature matching!

📖 Fundamentos Teóricos: Feature Detection & Matching¶

🎯 El Problema Fundamental¶

El desafío central en visión computacional es encontrar correspondencias entre imágenes que muestran el mismo objeto o escena bajo diferentes condiciones [1]. Las imágenes pueden sufrir transformaciones que dificultan el matching:

  • Transformaciones Geométricas: Traslación, rotación, cambios de escala, perspectiva [2]
  • Transformaciones Fotométricas: Variaciones de iluminación, contraste, exposición [2]

Resolver este problema es crucial para aplicaciones como reconstrucción 3D, SLAM (Simultaneous Localization and Mapping), image stitching y realidad aumentada [3].


🔄 Pipeline de Feature Matching¶

El proceso se divide en tres etapas fundamentales [4]:

┌─────────────┐      ┌──────────────┐      ┌─────────────┐
│  Detection  │ ───▶ │ Description  │ ───▶ │  Matching   │
│  (Keypoints)│      │ (Descriptors)│      │ (Correspondence)│
└─────────────┘      └──────────────┘      └─────────────┘
  1. Detección (Detection): Identificar puntos de interés distintivos en la imagen
  2. Descripción (Description): Computar un vector descriptor para cada keypoint
  3. Correspondencia (Matching): Comparar descriptores para encontrar matches

✨ Propiedades Deseadas de Features¶

Un buen detector/descriptor debe cumplir [5]:

Propiedad Descripción
Repetibilidad El mismo punto debe detectarse en múltiples vistas
Invarianza a Escala Robusto ante cambios de tamaño del objeto
Invarianza a Rotación Robusto ante rotaciones de la imagen
Invarianza a Iluminación Robusto ante cambios de brillo/contraste
Prominencia Detectar regiones distintivas y salientes
Localización Features bien localizados espacialmente

🔍 Algoritmos Implementados¶

1. Harris Corner Detector (Harris & Stephens, 1988) [6]¶

Concepto: Detecta esquinas mediante análisis de cambios de intensidad en múltiples direcciones.

Matemática: Función de "cornerness"

R = det(M) - k·trace(M)²

Donde M es la matriz de estructura del tensor de segundo momento.

Características:

  • ✅ Excelente localización de esquinas
  • ✅ Invariante a traslación y rotación
  • ❌ NO invariante a escala
  • 📊 Usado como detector, requiere descriptores adicionales (ej. ORB)

2. SIFT - Scale Invariant Feature Transform (Lowe, 2004) [7]¶

Concepto: Detector y descriptor robusto basado en extremos de Difference of Gaussians (DoG) en espacio de escalas.

Pipeline SIFT:

  1. Detección: Extremos locales en DoG (aproximación eficiente del Laplacian of Gaussian)
  2. Localización: Refinamiento sub-pixel de keypoints
  3. Orientación: Asignación de orientación dominante (invarianza a rotación)
  4. Descriptor: Histogramas de gradientes orientados en región 16×16 → vector 128D

Características:

  • ✅ Altamente robusto a escala, rotación, iluminación
  • ✅ Descriptor de 128 dimensiones muy distintivo
  • ✅ Tolerante a cambios de viewpoint hasta 30° [8]
  • ❌ Computacionalmente costoso
  • ⚖️ Patentado (expiró en 2020)

Descriptor SIFT: Región dividida en cuadrícula 4×4, cada celda con histograma de 8 orientaciones → 4×4×8 = 128 dimensiones


3. ORB - Oriented FAST and Rotated BRIEF (Rublee et al., 2011) [9]¶

Concepto: Alternativa rápida y libre a SIFT/SURF, combina detector FAST con descriptor BRIEF modificado.

Componentes:

  • Detector: FAST (Features from Accelerated Segment Test) con medida de Harris
  • Descriptor: BRIEF (Binary Robust Independent Elementary Features) con orientación
  • Orientación: Basada en intensity centroid para invarianza a rotación

Características:

  • ✅ Muy rápido (2 órdenes de magnitud más rápido que SIFT)
  • ✅ Descriptor binario → matching eficiente (distancia de Hamming)
  • ✅ Libre de patentes y open-source
  • ❌ Menos robusto que SIFT ante transformaciones extremas
  • 🚀 Ideal para aplicaciones en tiempo real (robótica móvil, drones)

Descriptor ORB: Vector binario de 256 bits (vs 128 floats de SIFT)


🔗 Feature Matching¶

Una vez extraídos los descriptores, se realiza el matching mediante:

Brute Force Matcher:

  • SIFT: Norma L2 (distancia Euclidiana) para descriptores de punto flotante
  • ORB/Harris: Norma de Hamming para descriptores binarios

Ratio Test (Lowe's Ratio Test) [7]:

ratio = ||descriptor1 - best_match|| / ||descriptor1 - second_best_match||
if ratio < 0.75:  # threshold típico
    accept_match()

Este test filtra matches ambiguos, mejorando la precisión del matching.


📊 Comparación Rápida¶

Algoritmo Invarianza Escala Invarianza Rotación Velocidad Descriptor
Harris ❌ ✅ ⚡⚡⚡ Requiere externo
SIFT ✅ ✅ 🐢 128D float
ORB Limitada ✅ ⚡⚡⚡ 256 bits

📚 Referencias¶

[1] Ochoa Ruiz, G. (2024). Módulo 3.2 - Extracción de Descriptores. Visión Computacional para Imágenes y Video, Tec de Monterrey.

[2] Szeliski, R. (2022). Computer Vision: Algorithms and Applications (2nd ed.). Springer.

[3] Hartley, R., & Zisserman, A. (2004). Multiple View Geometry in Computer Vision (2nd ed.). Cambridge University Press.

[4] Tuytelaars, T., & Mikolajczyk, K. (2008). Local Invariant Feature Detectors: A Survey. Foundations and Trends in Computer Graphics and Vision, 3(3), 177-280.

[5] Schmid, C., Mohr, R., & Bauckhage, C. (2000). Evaluation of Interest Point Detectors. International Journal of Computer Vision, 37(2), 151-172.

[6] Harris, C., & Stephens, M. (1988). A Combined Corner and Edge Detector. Alvey Vision Conference, 147-151.

[7] Lowe, D. G. (2004). Distinctive Image Features from Scale-Invariant Keypoints. International Journal of Computer Vision, 60(2), 91-110. DOI: 10.1023/B:VISI.0000029664.99615.94

[8] Mikolajczyk, K., & Schmid, C. (2005). A Performance Evaluation of Local Descriptors. IEEE Transactions on Pattern Analysis and Machine Intelligence, 27(10), 1615-1630.

[9] Rublee, E., Rabaud, V., Konolige, K., & Bradski, G. (2011). ORB: An Efficient Alternative to SIFT or SURF. IEEE International Conference on Computer Vision (ICCV), 2564-2571.

[10] OpenCV Documentation. (2024). Feature Detection and Description. Retrieved from https://docs.opencv.org/4.x/db/d27/tutorial_py_table_of_contents_feature2d.html

[11] Solem, J. E. (2012). Programming Computer Vision with Python. O'Reilly Media.


💡 Nota: Esta actividad se enfoca en implementación práctica y análisis comparativo empírico de estos algoritmos bajo diferentes condiciones de transformación.

📑 Tabla de Contenidos¶

  1. Importación de Librerías
  2. Descarga y Preparación de Imágenes
  3. ORB (Oriented FAST and Rotated BRIEF)
  4. Creación de Imagen de Prueba con Invarianza
  5. Visualización de Imágenes de Entrenamiento y Prueba
  6. Detección de Keypoints con ORB
  7. SIFT Matching (Scale Invariant Feature Transform)
  8. Matching entre Diferentes Imágenes
  9. Extracción de Keypoints con SIFT
  10. Feature Matching Comparativo
  11. Análisis Comparativo de Algoritmos
  12. Conclusiones Personales
  13. Reflexión Final del Equipo

1. Importación de Librerías ¶

Importamos las bibliotecas fundamentales para procesamiento de imágenes, detección de características y visualización de resultados.

In [1]:
# Librerías para procesamiento de imágenes y visión computacional
import cv2
import numpy as np

# Librerías para visualización
import matplotlib.pyplot as plt
import seaborn as sns

# Librerías para descarga de archivos
import gdown
import os
from pathlib import Path

# Configuración de visualización
plt.style.use('seaborn-v0_8-darkgrid')
sns.set_palette("husl")
%matplotlib inline

print("✅ Librerías importadas correctamente")
print(f"OpenCV version: {cv2.__version__}")
✅ Librerías importadas correctamente
OpenCV version: 4.12.0

2. Descarga y Preparación de Imágenes ¶

Descargamos las imágenes desde Google Drive usando gdown y las preparamos para el procesamiento. Incluye funciones genéricas para redimensionar y convertir a escala de grises.

In [2]:
# Diccionario con las imágenes y sus IDs de Google Drive
IMAGE_DATASET = {
    '01_Wall-E_Front_Environment1.jpg': '1qXaPcFsA9w4ce7fVvXkvauFiOKPrOiB8',
    '02_Wall-E_Right_Side_Environment1.jpg': '1O7v9FJOuynI1gW28FR3mJawKMxHjaIg2',
    '03_Wall-E_Right_Side_Environment2.jpg': '1aNybti8wTIswStoUcvDaf6ss6KhTljh7',
    '04_Wall-E_Left_Side_Environment2.jpg': '1ALYDEu_36moDBnAuOONAuXCflc8pUUKK',
    '05_Wall-E_Zoom_Out_Up_Environment2.jpg': '1Wzrj4FhvWGt9xI7kymqG08G2jmJCc_rO',
    '06_Wall-E_Close_Up_Front_Environment2.jpg': '1BDq3VTfukq1CeyS-GgwhgTTvR4-78TtV',
    '07_Wall-E_Laydown_Environment2.jpg': '1gwoIszT95veji_ITv5O_Mk9wAgUOFUw5',
    '08_Wall-E_Upside_Down_Environment2.jpg': '1sPM-ZGM0PZONefeXtYysR3WvxJLuO-O4',
    '09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg': '1VKJWf6KoA2SPv_T5qFPJzGry5RlruPqz',
    '10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg': '1SMEbXT9bLlzONqgT7IKU7J096dFnA2D2',
    '11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg': '1ApvBwr8ox-IJAnGnQ_vpSCBfeJAdZCTi',
    '12_Wall-E_Laydown_withObjects_Environment1.jpg': '1vOkPFIsVJDhWtkXZlzneLU6-sn4u1aHj',
    '13_Wall-E_Front_Outside_Environment3.jpg': '1FRlGdGAJn26Wg5w5Qfx-ZsBO2ztsASxZ',
    '14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg': '1dd8tAPz8Jqe_L0Liyyko5HRhewypJWb_',
    '15_PopeyePuzzle_Front.jpg': '1tjEmHfm3UZmIkt7blOgGcsJ-AyKjmUfk',
    '16_PopeyePuzzle_MorePuzzles.jpg': '1Dq2s1kKf244yRqTPr7ascnTo8LQ_Vj_C',
    'elon_1.jpg': '1IQegnvUTqsOo0BFlwrRZ0LA9vgoA5NY1',
    'elon_2.png': '1MkG74Tzwl8BTUw4f3C7jZTnjB0uVFc3R'
}

# Crear directorio para las imágenes
DATA_DIR = Path('data')
DATA_DIR.mkdir(exist_ok=True)

def download_images(image_dict: dict, output_dir: Path) -> None:
    """
    Descarga imágenes desde Google Drive usando gdown.

    Args:
        image_dict: Diccionario con nombres de archivo como keys e IDs de Drive como values
        output_dir: Directorio donde se guardarán las imágenes
    """
    for filename, file_id in image_dict.items():
        output_path = output_dir / filename

        if output_path.exists():
            print(f"⏭️  {filename} ya existe, omitiendo descarga")
            continue

        url = f'https://drive.google.com/uc?id={file_id}'
        print(f"📥 Descargando {filename}...")
        gdown.download(url, str(output_path), quiet=False)

    print("\n✅ Todas las imágenes descargadas correctamente")

# Descargar imágenes
download_images(IMAGE_DATASET, DATA_DIR)
📥 Descargando 01_Wall-E_Front_Environment1.jpg...
Downloading...
From: https://drive.google.com/uc?id=1qXaPcFsA9w4ce7fVvXkvauFiOKPrOiB8
To: /content/data/01_Wall-E_Front_Environment1.jpg
100%|██████████| 331k/331k [00:00<00:00, 35.5MB/s]
📥 Descargando 02_Wall-E_Right_Side_Environment1.jpg...
Downloading...
From: https://drive.google.com/uc?id=1O7v9FJOuynI1gW28FR3mJawKMxHjaIg2
To: /content/data/02_Wall-E_Right_Side_Environment1.jpg
100%|██████████| 306k/306k [00:00<00:00, 8.43MB/s]
📥 Descargando 03_Wall-E_Right_Side_Environment2.jpg...
Downloading...
From: https://drive.google.com/uc?id=1aNybti8wTIswStoUcvDaf6ss6KhTljh7
To: /content/data/03_Wall-E_Right_Side_Environment2.jpg
100%|██████████| 500k/500k [00:00<00:00, 14.1MB/s]
📥 Descargando 04_Wall-E_Left_Side_Environment2.jpg...
Downloading...
From: https://drive.google.com/uc?id=1ALYDEu_36moDBnAuOONAuXCflc8pUUKK
To: /content/data/04_Wall-E_Left_Side_Environment2.jpg
100%|██████████| 500k/500k [00:00<00:00, 9.01MB/s]
📥 Descargando 05_Wall-E_Zoom_Out_Up_Environment2.jpg...
Downloading...
From: https://drive.google.com/uc?id=1Wzrj4FhvWGt9xI7kymqG08G2jmJCc_rO
To: /content/data/05_Wall-E_Zoom_Out_Up_Environment2.jpg
100%|██████████| 696k/696k [00:00<00:00, 30.7MB/s]
📥 Descargando 06_Wall-E_Close_Up_Front_Environment2.jpg...
Downloading...
From: https://drive.google.com/uc?id=1BDq3VTfukq1CeyS-GgwhgTTvR4-78TtV
To: /content/data/06_Wall-E_Close_Up_Front_Environment2.jpg
100%|██████████| 451k/451k [00:00<00:00, 12.6MB/s]
📥 Descargando 07_Wall-E_Laydown_Environment2.jpg...
Downloading...
From: https://drive.google.com/uc?id=1gwoIszT95veji_ITv5O_Mk9wAgUOFUw5
To: /content/data/07_Wall-E_Laydown_Environment2.jpg
100%|██████████| 638k/638k [00:00<00:00, 15.0MB/s]
📥 Descargando 08_Wall-E_Upside_Down_Environment2.jpg...
Downloading...
From: https://drive.google.com/uc?id=1sPM-ZGM0PZONefeXtYysR3WvxJLuO-O4
To: /content/data/08_Wall-E_Upside_Down_Environment2.jpg
100%|██████████| 438k/438k [00:00<00:00, 9.58MB/s]
📥 Descargando 09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg...
Downloading...
From: https://drive.google.com/uc?id=1VKJWf6KoA2SPv_T5qFPJzGry5RlruPqz
To: /content/data/09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg
100%|██████████| 315k/315k [00:00<00:00, 6.91MB/s]
📥 Descargando 10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg...
Downloading...
From: https://drive.google.com/uc?id=1SMEbXT9bLlzONqgT7IKU7J096dFnA2D2
To: /content/data/10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg
100%|██████████| 388k/388k [00:00<00:00, 11.7MB/s]
📥 Descargando 11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg...
Downloading...
From: https://drive.google.com/uc?id=1ApvBwr8ox-IJAnGnQ_vpSCBfeJAdZCTi
To: /content/data/11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg
100%|██████████| 333k/333k [00:00<00:00, 16.4MB/s]
📥 Descargando 12_Wall-E_Laydown_withObjects_Environment1.jpg...
Downloading...
From: https://drive.google.com/uc?id=1vOkPFIsVJDhWtkXZlzneLU6-sn4u1aHj
To: /content/data/12_Wall-E_Laydown_withObjects_Environment1.jpg
100%|██████████| 610k/610k [00:00<00:00, 11.7MB/s]
📥 Descargando 13_Wall-E_Front_Outside_Environment3.jpg...
Downloading...
From: https://drive.google.com/uc?id=1FRlGdGAJn26Wg5w5Qfx-ZsBO2ztsASxZ
To: /content/data/13_Wall-E_Front_Outside_Environment3.jpg
100%|██████████| 1.05M/1.05M [00:00<00:00, 39.0MB/s]
📥 Descargando 14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg...
Downloading...
From: https://drive.google.com/uc?id=1dd8tAPz8Jqe_L0Liyyko5HRhewypJWb_
To: /content/data/14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg
100%|██████████| 457k/457k [00:00<00:00, 12.7MB/s]
📥 Descargando 15_PopeyePuzzle_Front.jpg...
Downloading...
From: https://drive.google.com/uc?id=1tjEmHfm3UZmIkt7blOgGcsJ-AyKjmUfk
To: /content/data/15_PopeyePuzzle_Front.jpg
100%|██████████| 433k/433k [00:00<00:00, 12.0MB/s]
📥 Descargando 16_PopeyePuzzle_MorePuzzles.jpg...
Downloading...
From: https://drive.google.com/uc?id=1Dq2s1kKf244yRqTPr7ascnTo8LQ_Vj_C
To: /content/data/16_PopeyePuzzle_MorePuzzles.jpg
100%|██████████| 774k/774k [00:00<00:00, 13.0MB/s]
📥 Descargando elon_1.jpg...
Downloading...
From: https://drive.google.com/uc?id=1IQegnvUTqsOo0BFlwrRZ0LA9vgoA5NY1
To: /content/data/elon_1.jpg
100%|██████████| 28.7k/28.7k [00:00<00:00, 29.7MB/s]
📥 Descargando elon_2.png...
Downloading...
From: https://drive.google.com/uc?id=1MkG74Tzwl8BTUw4f3C7jZTnjB0uVFc3R
To: /content/data/elon_2.png
100%|██████████| 280k/280k [00:00<00:00, 10.5MB/s]
✅ Todas las imágenes descargadas correctamente

In [3]:
def load_and_preprocess_image(image_path: str, max_width: int = 800,
                             convert_to_gray: bool = True) -> tuple:
    """
    Carga y preprocesa una imagen: redimensiona y convierte a escala de grises.

    Args:
        image_path: Ruta de la imagen
        max_width: Ancho máximo para redimensionar
        convert_to_gray: Si True, convierte a escala de grises

    Returns:
        Tupla (imagen_color, imagen_gris) o (imagen_color, None) si convert_to_gray=False
    """
    # Leer imagen
    img = cv2.imread(image_path)
    if img is None:
        raise FileNotFoundError(f"No se pudo cargar la imagen: {image_path}")

    # Redimensionar si es necesario
    height, width = img.shape[:2]
    if width > max_width:
        scale = max_width / width
        new_width = max_width
        new_height = int(height * scale)
        img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA)

    # Convertir BGR a RGB para visualización correcta
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

    # Convertir a escala de grises si se requiere
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if convert_to_gray else None

    return img_rgb, img_gray

def display_image_pair(img1: np.ndarray, img2: np.ndarray,
                       title1: str = "Imagen 1", title2: str = "Imagen 2",
                       figsize: tuple = (16, 6), cmap: str = None) -> None:
    """
    Visualiza dos imágenes lado a lado.

    Args:
        img1, img2: Imágenes a visualizar
        title1, title2: Títulos de las imágenes
        figsize: Tamaño de la figura
        cmap: Mapa de colores (ej. 'gray' para escala de grises)
    """
    fig, axes = plt.subplots(1, 2, figsize=figsize)

    axes[0].imshow(img1, cmap=cmap)
    axes[0].set_title(title1, fontsize=12, fontweight='bold')
    axes[0].axis('off')

    axes[1].imshow(img2, cmap=cmap)
    axes[1].set_title(title2, fontsize=12, fontweight='bold')
    axes[1].axis('off')

    plt.tight_layout()
    plt.show()

print("✅ Funciones de utilidad definidas")
✅ Funciones de utilidad definidas

3. ORB (Oriented FAST and Rotated BRIEF) ¶

ORB fue desarrollado en OpenCV Labs (2011) como una alternativa eficiente y libre a SIFT y SURF. Combina el detector de keypoints FAST con el descriptor binario BRIEF, añadiendo invarianza a la rotación y mejorando la robustez.

In [4]:
# Cargar imagen de demostración
img_color, img_gray = load_and_preprocess_image('data/elon_1.jpg')

# Visualizar imagen original
display_image_pair(img_color, img_gray,
                   title1="Imagen Original (Color)",
                   title2="Imagen en Escala de Grises",
                   cmap='gray')
No description has been provided for this image

4. Creación de Imagen de Prueba con Invarianza ¶

Para evaluar la robustez de los descriptores, creamos una imagen de prueba aplicando transformaciones de escala (pirámide gaussiana) y rotación. Esto permite verificar la invarianza de los algoritmos ante estas transformaciones geométricas.

In [5]:
def create_transformed_image(img: np.ndarray, scale_levels: int = 2,
                            rotation_angle: float = 30) -> tuple:
    """
    Crea una versión transformada de la imagen con cambios de escala y rotación.

    Args:
        img: Imagen original
        scale_levels: Número de niveles de pirámide (escala)
        rotation_angle: Ángulo de rotación en grados

    Returns:
        Tupla (imagen_transformada_color, imagen_transformada_gray)
    """
    # Aplicar reducción de escala mediante pirámide gaussiana
    transformed = img.copy()
    for _ in range(scale_levels):
        transformed = cv2.pyrDown(transformed)

    # Aplicar rotación
    num_rows, num_cols = transformed.shape[:2]
    center = (num_cols / 2, num_rows / 2)
    rotation_matrix = cv2.getRotationMatrix2D(center, rotation_angle, 1.0)
    transformed = cv2.warpAffine(transformed, rotation_matrix, (num_cols, num_rows))

    # Convertir a escala de grises
    transformed_gray = cv2.cvtColor(transformed, cv2.COLOR_RGB2GRAY)

    return transformed, transformed_gray

# Crear imagen de prueba con transformaciones
test_img_color, test_img_gray = create_transformed_image(img_color,
                                                          scale_levels=2,
                                                          rotation_angle=30)

print(f"✅ Imagen transformada creada")
print(f"   - Escala reducida: 2 niveles de pirámide")
print(f"   - Rotación aplicada: 30 grados")
print(f"   - Tamaño original: {img_color.shape[:2]}")
print(f"   - Tamaño transformado: {test_img_color.shape[:2]}")
✅ Imagen transformada creada
   - Escala reducida: 2 niveles de pirámide
   - Rotación aplicada: 30 grados
   - Tamaño original: (357, 632)
   - Tamaño transformado: (90, 158)

5. Visualización de Imágenes de Entrenamiento y Prueba ¶

Comparamos visualmente la imagen original (entrenamiento) con la imagen transformada (prueba).

In [6]:
display_image_pair(img_color, test_img_color,
                   title1="Imagen de Entrenamiento (Original)",
                   title2="Imagen de Prueba (Escalada + Rotada)",
                   figsize=(18, 7))
No description has been provided for this image

6. Detección de Keypoints con ORB ¶

Detectamos keypoints y computamos descriptores usando ORB. Visualizamos los keypoints detectados con y sin información de tamaño y orientación.

In [7]:
# Crear detector ORB
orb = cv2.ORB_create(nfeatures=1000)

# Detectar keypoints y computar descriptores
train_keypoints, train_descriptors = orb.detectAndCompute(img_color, None)
test_keypoints, test_descriptors = orb.detectAndCompute(test_img_gray, None)

print(f"🔍 Keypoints detectados:")
print(f"   - Imagen de entrenamiento: {len(train_keypoints)}")
print(f"   - Imagen de prueba: {len(test_keypoints)}")

# Visualizar keypoints
img_keypoints_simple = cv2.drawKeypoints(img_color, train_keypoints, None,
                                          color=(0, 255, 0))
img_keypoints_rich = cv2.drawKeypoints(img_color, train_keypoints, None,
                                        flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

display_image_pair(img_keypoints_rich, img_keypoints_simple,
                   title1="Keypoints con Tamaño y Orientación",
                   title2="Keypoints Simples",
                   figsize=(18, 7))
🔍 Keypoints detectados:
   - Imagen de entrenamiento: 960
   - Imagen de prueba: 39
No description has been provided for this image

Matching con ORB¶

Utilizamos Brute Force Matcher con distancia de Hamming (apropiada para descriptores binarios) para encontrar correspondencias entre keypoints.

In [8]:
# Crear Brute Force Matcher para descriptores binarios
bf_matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# Realizar matching
matches = bf_matcher.match(train_descriptors, test_descriptors)
matches = sorted(matches, key=lambda x: x.distance)

# Visualizar mejores matches
num_matches_to_show = min(50, len(matches))
img_matches = cv2.drawMatches(img_color, train_keypoints,
                              test_img_gray, test_keypoints,
                              matches[:num_matches_to_show], None,
                              flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

plt.figure(figsize=(18, 8))
plt.title(f'ORB - Mejores {num_matches_to_show} Matches', fontsize=14, fontweight='bold')
plt.imshow(img_matches)
plt.axis('off')
plt.tight_layout()
plt.show()

print(f"\n📊 Estadísticas de Matching ORB:")
print(f"   - Total de matches encontrados: {len(matches)}")
print(f"   - Distancia promedio (top 50): {np.mean([m.distance for m in matches[:50]]):.2f}")
No description has been provided for this image
📊 Estadísticas de Matching ORB:
   - Total de matches encontrados: 30
   - Distancia promedio (top 50): 54.07

7. SIFT Matching (Scale Invariant Feature Transform) ¶

SIFT es uno de los algoritmos más robustos para detección de características, ofreciendo invarianza a escala, rotación, iluminación y cambios moderados de perspectiva. Utiliza Difference of Gaussians (DoG) para detección y histogramas de gradientes orientados para descripción.

In [9]:
# Cargar imagen y convertir a escala de grises
img_sift, gray_sift = load_and_preprocess_image('data/elon_1.jpg')

# Crear detector SIFT
sift = cv2.SIFT_create()

# Detectar keypoints y descriptores
keypoints_sift, descriptors_sift = sift.detectAndCompute(img_sift, None)

# Dibujar keypoints
img_with_keypoints = cv2.drawKeypoints(gray_sift, keypoints_sift, img_sift,
                                        flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

plt.figure(figsize=(14, 8))
plt.title(f'SIFT Keypoints Detectados: {len(keypoints_sift)}',
          fontsize=14, fontweight='bold')
plt.imshow(img_with_keypoints)
plt.axis('off')
plt.tight_layout()
plt.show()

print(f"🔍 Keypoints SIFT detectados: {len(keypoints_sift)}")
No description has been provided for this image
🔍 Keypoints SIFT detectados: 293

8. Matching entre Diferentes Imágenes ¶

Evaluamos el matching entre dos imágenes diferentes de la misma persona con variaciones en pose e iluminación.

In [10]:
# Cargar par de imágenes
img1_color, img1_gray = load_and_preprocess_image('data/elon_1.jpg')
img2_color, img2_gray = load_and_preprocess_image('data/elon_2.png')

# Visualizar ambas imágenes
display_image_pair(img1_gray, img2_gray,
                   title1="Imagen 1 - Elon Musk",
                   title2="Imagen 2 - Elon Musk (Diferente Pose)",
                   cmap='gray')
No description has been provided for this image

9. Extracción de Keypoints con SIFT ¶

Extraemos keypoints y descriptores para ambas imágenes usando SIFT.

In [11]:
# Detectar keypoints y descriptores en ambas imágenes
sift = cv2.SIFT_create()

kp1, desc1 = sift.detectAndCompute(img1_gray, None)
kp2, desc2 = sift.detectAndCompute(img2_gray, None)

print(f"🔍 Keypoints extraídos:")
print(f"   - Imagen 1: {len(kp1)} keypoints")
print(f"   - Imagen 2: {len(kp2)} keypoints")
🔍 Keypoints extraídos:
   - Imagen 1: 296 keypoints
   - Imagen 2: 390 keypoints

10. Feature Matching Comparativo ¶

Realizamos matching usando Brute Force Matcher con norma L2 (apropiada para descriptores SIFT de punto flotante).

In [12]:
# Crear matcher con norma L2
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)

# Realizar matching
matches = bf.match(desc1, desc2)
matches = sorted(matches, key=lambda x: x.distance)

# Visualizar top 50 matches
img_matches = cv2.drawMatches(img1_gray, kp1, img2_gray, kp2,
                              matches[:50], None,
                              flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

plt.figure(figsize=(18, 8))
plt.title('SIFT - Top 50 Matches', fontsize=14, fontweight='bold')
plt.imshow(img_matches, cmap='gray')
plt.axis('off')
plt.tight_layout()
plt.show()

print(f"\n📊 Total de matches encontrados: {len(matches)}")
No description has been provided for this image
📊 Total de matches encontrados: 102

11. Análisis Comparativo de Algoritmos ¶

Implementamos un framework completo para comparar Harris + ORB, SIFT y ORB en múltiples pares de imágenes. Evaluamos la calidad del matching mediante la distancia promedio de los matches.

Harris Corner Detection¶

Harris detecta esquinas (regiones con cambios de intensidad significativos en múltiples direcciones). Aunque no es invariante a escala, proporciona puntos muy estables y repetibles. Lo combinamos con descriptores ORB para el matching.

In [13]:
class FeatureMatchingComparator:
    """
    Clase para comparar algoritmos de feature matching.
    Implementa Harris, SIFT y ORB con métricas de evaluación.
    """

    def __init__(self):
        self.metrics = {
            'image_pairs': [],
            'sift_50': [], 'sift_all': [],
            'orb_50': [], 'orb_all': [],
            'harris_50': [], 'harris_all': [],
            'keypoints_sift': [], 'keypoints_orb': [], 'keypoints_harris': [],
            'matches_sift': [], 'matches_orb': [], 'matches_harris': []
        }
        self.orb = cv2.ORB_create(nfeatures=1000)
        self.sift = cv2.SIFT_create()

    def _extract_harris_keypoints(self, img_gray: np.ndarray) -> list:
        """
        Extrae keypoints usando Harris Corner Detector.

        Args:
            img_gray: Imagen en escala de grises

        Returns:
            Lista de cv2.KeyPoint objetos
        """
        harris_corners = cv2.cornerHarris(img_gray, blockSize=2, ksize=3, k=0.04)
        harris_corners = cv2.dilate(harris_corners, None)

        # Umbral para seleccionar esquinas prominentes
        threshold = 0.01 * harris_corners.max()
        keypoint_coords = np.argwhere(harris_corners > threshold)

        # Convertir coordenadas a KeyPoint objects
        keypoints = [cv2.KeyPoint(float(pt[1]), float(pt[0]), 1)
                    for pt in keypoint_coords]

        return keypoints

    def _compute_average_distance(self, matches: list, num_matches: int) -> float:
        """
        Calcula distancia promedio de los primeros N matches.

        Args:
            matches: Lista de DMatch objects
            num_matches: Número de matches a considerar

        Returns:
            Distancia promedio
        """
        if not matches or num_matches <= 0:
            return 0.0

        subset = matches[:min(num_matches, len(matches))]
        return sum(m.distance for m in subset) / len(subset)

    def _format_image_pair_name(self, path1: str, path2: str) -> str:
        """
        Formatea nombres de archivos para etiquetas de gráficos.
        """
        name1 = Path(path1).stem[:15]
        name2 = Path(path2).stem[:15]
        return f"{name1}\nvs\n{name2}"

    def _plot_matches(self, title: str, img1: np.ndarray, kp1: list,
                     img2: np.ndarray, kp2: list, matches: list,
                     num_matches: int) -> None:
        """
        Visualiza matches entre dos imágenes.
        """
        matches_to_draw = matches[:min(num_matches, len(matches))]
        img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches_to_draw, None,
                                     flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)

        plt.figure(figsize=(18, 6))
        plt.title(f'{title} - {len(matches_to_draw)} Matches',
                 fontsize=13, fontweight='bold')
        plt.imshow(img_matches, cmap='gray')
        plt.axis('off')
        plt.tight_layout()
        plt.show()

    def compare_algorithms(self, img1_path: str, img2_path: str,
                          visualize: bool = True) -> dict:
        """
        Compara Harris, SIFT y ORB en un par de imágenes.

        Args:
            img1_path: Ruta de primera imagen
            img2_path: Ruta de segunda imagen
            visualize: Si True, muestra visualizaciones

        Returns:
            Diccionario con métricas del par de imágenes
        """
        # Cargar imágenes
        _, img1_gray = load_and_preprocess_image(img1_path)
        _, img2_gray = load_and_preprocess_image(img2_path)

        pair_name = self._format_image_pair_name(img1_path, img2_path)

        if visualize:
            display_image_pair(img1_gray, img2_gray,
                             title1=Path(img1_path).name,
                             title2=Path(img2_path).name,
                             cmap='gray', figsize=(16, 5))

        results = {}

        # ========== HARRIS + ORB DESCRIPTORS ==========
        kp1_harris = self._extract_harris_keypoints(img1_gray)
        kp2_harris = self._extract_harris_keypoints(img2_gray)
        _, desc1_harris = self.orb.compute(img1_gray, kp1_harris)
        _, desc2_harris = self.orb.compute(img2_gray, kp2_harris)

        if desc1_harris is not None and desc2_harris is not None:
            bf_harris = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
            matches_harris = sorted(bf_harris.match(desc1_harris, desc2_harris),
                                   key=lambda x: x.distance)

            results['harris'] = {
                'keypoints': (len(kp1_harris), len(kp2_harris)),
                'matches': len(matches_harris),
                'avg_dist_50': self._compute_average_distance(matches_harris, 50),
                'avg_dist_all': self._compute_average_distance(matches_harris, len(matches_harris))
            }

            if visualize:
                self._plot_matches('Harris + ORB Descriptors', img1_gray, kp1_harris,
                                 img2_gray, kp2_harris, matches_harris, 50)

        # ========== SIFT ==========
        kp1_sift, desc1_sift = self.sift.detectAndCompute(img1_gray, None)
        kp2_sift, desc2_sift = self.sift.detectAndCompute(img2_gray, None)

        bf_sift = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
        matches_sift = sorted(bf_sift.match(desc1_sift, desc2_sift),
                             key=lambda x: x.distance)

        results['sift'] = {
            'keypoints': (len(kp1_sift), len(kp2_sift)),
            'matches': len(matches_sift),
            'avg_dist_50': self._compute_average_distance(matches_sift, 50),
            'avg_dist_all': self._compute_average_distance(matches_sift, len(matches_sift))
        }

        if visualize:
            self._plot_matches('SIFT', img1_gray, kp1_sift,
                             img2_gray, kp2_sift, matches_sift, 50)

        # ========== ORB ==========
        kp1_orb, desc1_orb = self.orb.detectAndCompute(img1_gray, None)
        kp2_orb, desc2_orb = self.orb.detectAndCompute(img2_gray, None)

        bf_orb = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
        matches_orb = sorted(bf_orb.match(desc1_orb, desc2_orb),
                            key=lambda x: x.distance)

        results['orb'] = {
            'keypoints': (len(kp1_orb), len(kp2_orb)),
            'matches': len(matches_orb),
            'avg_dist_50': self._compute_average_distance(matches_orb, 50),
            'avg_dist_all': self._compute_average_distance(matches_orb, len(matches_orb))
        }

        if visualize:
            self._plot_matches('ORB', img1_gray, kp1_orb,
                             img2_gray, kp2_orb, matches_orb, 50)

        # Almacenar métricas
        self.metrics['image_pairs'].append(pair_name)
        self.metrics['sift_50'].append(results['sift']['avg_dist_50'])
        self.metrics['sift_all'].append(results['sift']['avg_dist_all'])
        self.metrics['orb_50'].append(results['orb']['avg_dist_50'])
        self.metrics['orb_all'].append(results['orb']['avg_dist_all'])
        self.metrics['harris_50'].append(results['harris']['avg_dist_50'])
        self.metrics['harris_all'].append(results['harris']['avg_dist_all'])

        self.metrics['keypoints_sift'].append(results['sift']['keypoints'][0])
        self.metrics['keypoints_orb'].append(results['orb']['keypoints'][0])
        self.metrics['keypoints_harris'].append(results['harris']['keypoints'][0])

        self.metrics['matches_sift'].append(results['sift']['matches'])
        self.metrics['matches_orb'].append(results['orb']['matches'])
        self.metrics['matches_harris'].append(results['harris']['matches'])

        # Imprimir resultados
        print(f"\n{'='*60}")
        print(f"📊 Resultados para: {Path(img1_path).name} vs {Path(img2_path).name}")
        print(f"{'='*60}")

        for algo_name, algo_results in results.items():
            print(f"\n🔹 {algo_name.upper()}:")
            print(f"   Keypoints detectados: Img1={algo_results['keypoints'][0]}, "
                  f"Img2={algo_results['keypoints'][1]}")
            print(f"   Total matches: {algo_results['matches']}")
            print(f"   Distancia promedio (50 mejores): {algo_results['avg_dist_50']:.2f}")
            print(f"   Distancia promedio (todos): {algo_results['avg_dist_all']:.2f}")

        return results

# Crear instancia del comparador
comparator = FeatureMatchingComparator()

print("✅ Clase FeatureMatchingComparator inicializada")
✅ Clase FeatureMatchingComparator inicializada

Comparación Demo: Elon Musk¶

In [14]:
# Comparar imágenes de Elon Musk
results_elon = comparator.compare_algorithms('data/elon_1.jpg', 'data/elon_2.png')
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: elon_1.jpg vs elon_2.png
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=1125, Img2=1244
   Total matches: 152
   Distancia promedio (50 mejores): 17.26
   Distancia promedio (todos): 28.60

🔹 SIFT:
   Keypoints detectados: Img1=296, Img2=390
   Total matches: 102
   Distancia promedio (50 mejores): 204.85
   Distancia promedio (todos): 263.11

🔹 ORB:
   Keypoints detectados: Img1=967, Img2=999
   Total matches: 311
   Distancia promedio (50 mejores): 33.10
   Distancia promedio (todos): 49.56

Comparaciones: Wall-E en Diferentes Condiciones¶

Evaluamos el rendimiento de los algoritmos con el robot Wall-E bajo diversas transformaciones: rotación, escala, oclusión parcial y cambios de entorno.

In [15]:
# Wall-E: Mismo entorno, diferentes ángulos
comparator.compare_algorithms(
    'data/01_Wall-E_Front_Environment1.jpg',
    'data/02_Wall-E_Right_Side_Environment1.jpg'
)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 02_Wall-E_Right_Side_Environment1.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=6978
   Total matches: 618
   Distancia promedio (50 mejores): 22.80
   Distancia promedio (todos): 48.06

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=1290
   Total matches: 390
   Distancia promedio (50 mejores): 133.08
   Distancia promedio (todos): 275.55

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 256
   Distancia promedio (50 mejores): 39.00
   Distancia promedio (todos): 52.80
Out[15]:
{'harris': {'keypoints': (7251, 6978),
  'matches': 618,
  'avg_dist_50': 22.8,
  'avg_dist_all': 48.06310679611651},
 'sift': {'keypoints': (1389, 1290),
  'matches': 390,
  'avg_dist_50': 133.08342834472657,
  'avg_dist_all': 275.55492819761616},
 'orb': {'keypoints': (1000, 1000),
  'matches': 256,
  'avg_dist_50': 39.0,
  'avg_dist_all': 52.80078125}}
In [16]:
# Wall-E: Rotación extrema (upside down)
comparator.compare_algorithms(
    'data/01_Wall-E_Front_Environment1.jpg',
    'data/08_Wall-E_Upside_Down_Environment2.jpg'
)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 08_Wall-E_Upside_Down_Environment2.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=25636
   Total matches: 1135
   Distancia promedio (50 mejores): 24.44
   Distancia promedio (todos): 47.06

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=4724
   Total matches: 622
   Distancia promedio (50 mejores): 108.51
   Distancia promedio (todos): 258.08

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 246
   Distancia promedio (50 mejores): 42.46
   Distancia promedio (todos): 56.77
Out[16]:
{'harris': {'keypoints': (7251, 25636),
  'matches': 1135,
  'avg_dist_50': 24.44,
  'avg_dist_all': 47.05903083700441},
 'sift': {'keypoints': (1389, 4724),
  'matches': 622,
  'avg_dist_50': 108.50702880859374,
  'avg_dist_all': 258.07929614885825},
 'orb': {'keypoints': (1000, 1000),
  'matches': 246,
  'avg_dist_50': 42.46,
  'avg_dist_all': 56.77235772357724}}
In [17]:
# Wall-E: Cambio de escala (zoom out)
comparator.compare_algorithms(
    'data/01_Wall-E_Front_Environment1.jpg',
    'data/05_Wall-E_Zoom_Out_Up_Environment2.jpg'
)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 05_Wall-E_Zoom_Out_Up_Environment2.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=99433
   Total matches: 1647
   Distancia promedio (50 mejores): 25.32
   Distancia promedio (todos): 49.22

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=10632
   Total matches: 742
   Distancia promedio (50 mejores): 169.28
   Distancia promedio (todos): 283.92

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 239
   Distancia promedio (50 mejores): 52.40
   Distancia promedio (todos): 64.68
Out[17]:
{'harris': {'keypoints': (7251, 99433),
  'matches': 1647,
  'avg_dist_50': 25.32,
  'avg_dist_all': 49.22465088038859},
 'sift': {'keypoints': (1389, 10632),
  'matches': 742,
  'avg_dist_50': 169.28126541137695,
  'avg_dist_all': 283.91926306341537},
 'orb': {'keypoints': (1000, 1000),
  'matches': 239,
  'avg_dist_50': 52.4,
  'avg_dist_all': 64.68200836820084}}
In [18]:
# Wall-E: Oclusión parcial con objetos
comparator.compare_algorithms(
    'data/01_Wall-E_Front_Environment1.jpg',
    'data/12_Wall-E_Laydown_withObjects_Environment1.jpg'
)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 12_Wall-E_Laydown_withObjects_Environment1.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=29901
   Total matches: 1124
   Distancia promedio (50 mejores): 22.68
   Distancia promedio (todos): 45.84

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=3384
   Total matches: 526
   Distancia promedio (50 mejores): 95.13
   Distancia promedio (todos): 253.26

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 233
   Distancia promedio (50 mejores): 43.80
   Distancia promedio (todos): 57.51
Out[18]:
{'harris': {'keypoints': (7251, 29901),
  'matches': 1124,
  'avg_dist_50': 22.68,
  'avg_dist_all': 45.844306049822066},
 'sift': {'keypoints': (1389, 3384),
  'matches': 526,
  'avg_dist_50': 95.12756065368653,
  'avg_dist_all': 253.2586680002539},
 'orb': {'keypoints': (1000, 1000),
  'matches': 233,
  'avg_dist_50': 43.8,
  'avg_dist_all': 57.506437768240346}}
In [19]:
# Wall-E: Con otros objetos en la escena
comparator.compare_algorithms(
    'data/01_Wall-E_Front_Environment1.jpg',
    'data/09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg'
)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 09_Wall-E_And_His_Friends_CuyInFront_Environment1.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=7799
   Total matches: 645
   Distancia promedio (50 mejores): 23.98
   Distancia promedio (todos): 43.46

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=1129
   Total matches: 381
   Distancia promedio (50 mejores): 123.00
   Distancia promedio (todos): 254.66

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 241
   Distancia promedio (50 mejores): 36.72
   Distancia promedio (todos): 52.04
Out[19]:
{'harris': {'keypoints': (7251, 7799),
  'matches': 645,
  'avg_dist_50': 23.98,
  'avg_dist_all': 43.46201550387597},
 'sift': {'keypoints': (1389, 1129),
  'matches': 381,
  'avg_dist_50': 122.9985629272461,
  'avg_dist_all': 254.66115708238496},
 'orb': {'keypoints': (1000, 1000),
  'matches': 241,
  'avg_dist_50': 36.72,
  'avg_dist_all': 52.04149377593361}}
In [20]:
# Wall-E: Cambio de entorno completo
comparator.compare_algorithms(
    'data/01_Wall-E_Front_Environment1.jpg',
    'data/13_Wall-E_Front_Outside_Environment3.jpg'
)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 13_Wall-E_Front_Outside_Environment3.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=116800
   Total matches: 1782
   Distancia promedio (50 mejores): 24.30
   Distancia promedio (todos): 47.32

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=14589
   Total matches: 748
   Distancia promedio (50 mejores): 99.33
   Distancia promedio (todos): 268.20

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 242
   Distancia promedio (50 mejores): 35.96
   Distancia promedio (todos): 55.79
Out[20]:
{'harris': {'keypoints': (7251, 116800),
  'matches': 1782,
  'avg_dist_50': 24.3,
  'avg_dist_all': 47.32435465768799},
 'sift': {'keypoints': (1389, 14589),
  'matches': 748,
  'avg_dist_50': 99.3261750793457,
  'avg_dist_all': 268.1964029220336},
 'orb': {'keypoints': (1000, 1000),
  'matches': 242,
  'avg_dist_50': 35.96,
  'avg_dist_all': 55.789256198347104}}

Comparación: Puzzle de Popeye¶

In [21]:
# Puzzle: Diferentes vistas y oclusiones
comparator.compare_algorithms(
    'data/15_PopeyePuzzle_Front.jpg',
    'data/16_PopeyePuzzle_MorePuzzles.jpg'
)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 15_PopeyePuzzle_Front.jpg vs 16_PopeyePuzzle_MorePuzzles.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=16584, Img2=48348
   Total matches: 2408
   Distancia promedio (50 mejores): 18.68
   Distancia promedio (todos): 47.08

🔹 SIFT:
   Keypoints detectados: Img1=2450, Img2=4805
   Total matches: 1100
   Distancia promedio (50 mejores): 51.09
   Distancia promedio (todos): 212.60

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 287
   Distancia promedio (50 mejores): 32.24
   Distancia promedio (todos): 57.02
Out[21]:
{'harris': {'keypoints': (16584, 48348),
  'matches': 2408,
  'avg_dist_50': 18.68,
  'avg_dist_all': 47.08056478405316},
 'sift': {'keypoints': (2450, 4805),
  'matches': 1100,
  'avg_dist_50': 51.09270275115967,
  'avg_dist_all': 212.59723248741844},
 'orb': {'keypoints': (1000, 1000),
  'matches': 287,
  'avg_dist_50': 32.24,
  'avg_dist_all': 57.01742160278746}}

Comparaciones Adicionales: Wall-E¶

In [22]:
# Referencia base
base_image = 'data/01_Wall-E_Front_Environment1.jpg'

# Lista de imágenes para comparar
comparison_images = [
    'data/03_Wall-E_Right_Side_Environment2.jpg',
    'data/04_Wall-E_Left_Side_Environment2.jpg',
    'data/06_Wall-E_Close_Up_Front_Environment2.jpg',
    'data/07_Wall-E_Laydown_Environment2.jpg',
    'data/10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg',
    'data/11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg',
    'data/14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg'
]

# Ejecutar comparaciones
for img_path in comparison_images:
    comparator.compare_algorithms(base_image, img_path)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 03_Wall-E_Right_Side_Environment2.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=40962
   Total matches: 1342
   Distancia promedio (50 mejores): 26.14
   Distancia promedio (todos): 46.79

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=6820
   Total matches: 656
   Distancia promedio (50 mejores): 145.63
   Distancia promedio (todos): 274.80

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 233
   Distancia promedio (50 mejores): 45.68
   Distancia promedio (todos): 57.71
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 04_Wall-E_Left_Side_Environment2.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=37241
   Total matches: 1326
   Distancia promedio (50 mejores): 21.48
   Distancia promedio (todos): 45.71

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=6600
   Total matches: 669
   Distancia promedio (50 mejores): 135.13
   Distancia promedio (todos): 273.40

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 238
   Distancia promedio (50 mejores): 46.50
   Distancia promedio (todos): 59.34
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 06_Wall-E_Close_Up_Front_Environment2.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=30187
   Total matches: 1182
   Distancia promedio (50 mejores): 21.56
   Distancia promedio (todos): 45.18

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=5351
   Total matches: 683
   Distancia promedio (50 mejores): 119.29
   Distancia promedio (todos): 264.28

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 263
   Distancia promedio (50 mejores): 35.78
   Distancia promedio (todos): 53.68
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 07_Wall-E_Laydown_Environment2.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=27800
   Total matches: 1179
   Distancia promedio (50 mejores): 19.58
   Distancia promedio (todos): 45.48

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=9459
   Total matches: 750
   Distancia promedio (50 mejores): 113.21
   Distancia promedio (todos): 268.15

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 259
   Distancia promedio (50 mejores): 42.06
   Distancia promedio (todos): 57.88
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 10_Wall-E_And_His_Friends_Free_Front_Environment1.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=5745
   Total matches: 508
   Distancia promedio (50 mejores): 21.48
   Distancia promedio (todos): 43.71

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=937
   Total matches: 411
   Distancia promedio (50 mejores): 90.24
   Distancia promedio (todos): 219.69

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 279
   Distancia promedio (50 mejores): 29.14
   Distancia promedio (todos): 47.01
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 11_Wall-E_And_His_Friends_Right_Side_Behind_Environment1.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=3706
   Total matches: 381
   Distancia promedio (50 mejores): 26.10
   Distancia promedio (todos): 48.46

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=961
   Total matches: 298
   Distancia promedio (50 mejores): 154.76
   Distancia promedio (todos): 267.39

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 236
   Distancia promedio (50 mejores): 43.44
   Distancia promedio (todos): 55.72
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
============================================================
📊 Resultados para: 01_Wall-E_Front_Environment1.jpg vs 14_Wall-E_Front_BehindAmongUs_Enviroment2.jpg
============================================================

🔹 HARRIS:
   Keypoints detectados: Img1=7251, Img2=39092
   Total matches: 1272
   Distancia promedio (50 mejores): 25.82
   Distancia promedio (todos): 49.05

🔹 SIFT:
   Keypoints detectados: Img1=1389, Img2=6663
   Total matches: 637
   Distancia promedio (50 mejores): 163.92
   Distancia promedio (todos): 281.75

🔹 ORB:
   Keypoints detectados: Img1=1000, Img2=1000
   Total matches: 237
   Distancia promedio (50 mejores): 49.76
   Distancia promedio (todos): 61.42

Visualización de Resultados Agregados¶

In [23]:
def plot_algorithm_comparison(metrics: dict) -> None:
    """
    Genera gráficos de líneas comparando el rendimiento de los algoritmos.
    """
    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 12))

    x_labels = metrics['image_pairs']
    x_pos = range(len(x_labels))

    # Gráfico 1: Top 50 matches
    ax1.plot(x_pos, metrics['sift_50'], marker='o', linewidth=2,
             label='SIFT (50 mejores)', color='#3498db')
    ax1.plot(x_pos, metrics['orb_50'], marker='s', linewidth=2,
             label='ORB (50 mejores)', color='#2ecc71')
    ax1.plot(x_pos, metrics['harris_50'], marker='^', linewidth=2,
             label='Harris (50 mejores)', color='#e74c3c')

    ax1.set_xticks(x_pos)
    ax1.set_xticklabels(x_labels, rotation=45, ha='right', fontsize=8)
    ax1.set_ylabel('Distancia Promedio', fontsize=11, fontweight='bold')
    ax1.set_title('Comparación: Distancia Promedio de los 50 Mejores Matches',
                  fontsize=13, fontweight='bold')
    ax1.legend(loc='best', fontsize=10)
    ax1.grid(True, alpha=0.3)

    # Gráfico 2: Todos los matches
    ax2.plot(x_pos, metrics['sift_all'], marker='o', linewidth=2,
             label='SIFT (todos)', color='#1a5490')
    ax2.plot(x_pos, metrics['orb_all'], marker='s', linewidth=2,
             label='ORB (todos)', color='#1d8348')
    ax2.plot(x_pos, metrics['harris_all'], marker='^', linewidth=2,
             label='Harris (todos)', color='#943126')

    ax2.set_xticks(x_pos)
    ax2.set_xticklabels(x_labels, rotation=45, ha='right', fontsize=8)
    ax2.set_ylabel('Distancia Promedio', fontsize=11, fontweight='bold')
    ax2.set_title('Comparación: Distancia Promedio de Todos los Matches',
                  fontsize=13, fontweight='bold')
    ax2.legend(loc='best', fontsize=10)
    ax2.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

# Generar gráfico comparativo
plot_algorithm_comparison(comparator.metrics)
No description has been provided for this image
In [24]:
def plot_summary_statistics(metrics: dict) -> None:
    """
    Genera gráfico de barras con promedios generales.
    """
    labels = ['SIFT\n(50)', 'SIFT\n(Todos)', 'ORB\n(50)',
              'ORB\n(Todos)', 'Harris\n(50)', 'Harris\n(Todos)']

    averages = [
        np.mean(metrics['sift_50']),
        np.mean(metrics['sift_all']),
        np.mean(metrics['orb_50']),
        np.mean(metrics['orb_all']),
        np.mean(metrics['harris_50']),
        np.mean(metrics['harris_all'])
    ]

    colors = ['#3498db', '#1a5490', '#2ecc71', '#1d8348', '#e74c3c', '#943126']

    plt.figure(figsize=(12, 7))
    bars = plt.bar(labels, averages, color=colors, edgecolor='black', linewidth=1.2)

    plt.ylabel('Distancia Promedio', fontsize=12, fontweight='bold')
    plt.title('Resumen: Distancia Promedio por Algoritmo (Todas las Comparaciones)',
              fontsize=14, fontweight='bold', pad=20)
    plt.ylim(0, max(averages) * 1.2)
    plt.grid(axis='y', alpha=0.3)

    # Añadir valores sobre las barras
    for bar, value in zip(bars, averages):
        height = bar.get_height()
        plt.text(bar.get_x() + bar.get_width()/2., height,
                f'{value:.2f}',
                ha='center', va='bottom', fontsize=11, fontweight='bold')

    plt.tight_layout()
    plt.show()

plot_summary_statistics(comparator.metrics)
No description has been provided for this image

Tabla Comparativa Detallada¶

In [25]:
import pandas as pd

def create_comparison_table(metrics: dict) -> pd.DataFrame:
    """
    Crea tabla comparativa con todas las métricas.
    """
    df = pd.DataFrame({
        'Par de Imágenes': [pair.replace('\n', ' ') for pair in metrics['image_pairs']],
        'KP SIFT': metrics['keypoints_sift'],
        'KP ORB': metrics['keypoints_orb'],
        'KP Harris': metrics['keypoints_harris'],
        'Matches SIFT': metrics['matches_sift'],
        'Matches ORB': metrics['matches_orb'],
        'Matches Harris': metrics['matches_harris'],
        'SIFT Dist (50)': [f"{x:.2f}" for x in metrics['sift_50']],
        'ORB Dist (50)': [f"{x:.2f}" for x in metrics['orb_50']],
        'Harris Dist (50)': [f"{x:.2f}" for x in metrics['harris_50']]
    })

    return df

# Crear y mostrar tabla
results_table = create_comparison_table(comparator.metrics)
print("\n" + "="*120)
print("📊 TABLA COMPARATIVA DE RESULTADOS")
print("="*120)
print(results_table.to_string(index=False))
print("="*120)
========================================================================================================================
📊 TABLA COMPARATIVA DE RESULTADOS
========================================================================================================================
                   Par de Imágenes  KP SIFT  KP ORB  KP Harris  Matches SIFT  Matches ORB  Matches Harris SIFT Dist (50) ORB Dist (50) Harris Dist (50)
                  elon_1 vs elon_2      296     967       1125           102          311             152         204.85         33.10            17.26
01_Wall-E_Front vs 02_Wall-E_Right     1389    1000       7251           390          256             618         133.08         39.00            22.80
01_Wall-E_Front vs 08_Wall-E_Upsid     1389    1000       7251           622          246            1135         108.51         42.46            24.44
01_Wall-E_Front vs 05_Wall-E_Zoom_     1389    1000       7251           742          239            1647         169.28         52.40            25.32
01_Wall-E_Front vs 12_Wall-E_Laydo     1389    1000       7251           526          233            1124          95.13         43.80            22.68
01_Wall-E_Front vs 09_Wall-E_And_H     1389    1000       7251           381          241             645         123.00         36.72            23.98
01_Wall-E_Front vs 13_Wall-E_Front     1389    1000       7251           748          242            1782          99.33         35.96            24.30
15_PopeyePuzzle vs 16_PopeyePuzzle     2450    1000      16584          1100          287            2408          51.09         32.24            18.68
01_Wall-E_Front vs 03_Wall-E_Right     1389    1000       7251           656          233            1342         145.63         45.68            26.14
01_Wall-E_Front vs 04_Wall-E_Left_     1389    1000       7251           669          238            1326         135.13         46.50            21.48
01_Wall-E_Front vs 06_Wall-E_Close     1389    1000       7251           683          263            1182         119.29         35.78            21.56
01_Wall-E_Front vs 07_Wall-E_Laydo     1389    1000       7251           750          259            1179         113.21         42.06            19.58
01_Wall-E_Front vs 10_Wall-E_And_H     1389    1000       7251           411          279             508          90.24         29.14            21.48
01_Wall-E_Front vs 11_Wall-E_And_H     1389    1000       7251           298          236             381         154.76         43.44            26.10
01_Wall-E_Front vs 14_Wall-E_Front     1389    1000       7251           637          237            1272         163.92         49.76            25.82
========================================================================================================================

Estadísticas Agregadas¶

In [26]:
def print_summary_statistics(metrics: dict) -> None:
    """
    Imprime estadísticas resumidas de todos los experimentos.
    """
    print("\n" + "="*80)
    print("📈 ESTADÍSTICAS RESUMIDAS")
    print("="*80)

    algorithms = [
        ('SIFT', 'sift_50', 'sift_all'),
        ('ORB', 'orb_50', 'orb_all'),
        ('Harris', 'harris_50', 'harris_all')
    ]

    for name, key_50, key_all in algorithms:
        print(f"\n🔹 {name}:")
        print(f"   Distancia promedio (50 mejores matches):")
        print(f"      Media: {np.mean(metrics[key_50]):.2f} | "
              f"Desv. Std: {np.std(metrics[key_50]):.2f} | "
              f"Min: {np.min(metrics[key_50]):.2f} | "
              f"Max: {np.max(metrics[key_50]):.2f}")

        print(f"   Distancia promedio (todos los matches):")
        print(f"      Media: {np.mean(metrics[key_all]):.2f} | "
              f"Desv. Std: {np.std(metrics[key_all]):.2f} | "
              f"Min: {np.min(metrics[key_all]):.2f} | "
              f"Max: {np.max(metrics[key_all]):.2f}")

    print("\n" + "="*80)

print_summary_statistics(comparator.metrics)
================================================================================
📈 ESTADÍSTICAS RESUMIDAS
================================================================================

🔹 SIFT:
   Distancia promedio (50 mejores matches):
      Media: 127.10 | Desv. Std: 36.45 | Min: 51.09 | Max: 204.85
   Distancia promedio (todos los matches):
      Media: 261.26 | Desv. Std: 19.71 | Min: 212.60 | Max: 283.92

🔹 ORB:
   Distancia promedio (50 mejores matches):
      Media: 40.54 | Desv. Std: 6.47 | Min: 29.14 | Max: 52.40
   Distancia promedio (todos los matches):
      Media: 55.93 | Desv. Std: 4.32 | Min: 47.01 | Max: 64.68

🔹 Harris:
   Distancia promedio (50 mejores matches):
      Media: 22.77 | Desv. Std: 2.67 | Min: 17.26 | Max: 26.14
   Distancia promedio (todos los matches):
      Media: 45.40 | Desv. Std: 4.80 | Min: 28.60 | Max: 49.22

================================================================================

Hallazgos y Análisis¶

Observaciones Principales:¶

1. SIFT - El Más Robusto

  • ✅ Mayor número de keypoints detectados
  • ✅ Excelente invarianza a escala y rotación
  • ✅ Distancias de matching consistentemente bajas
  • ❌ Mayor costo computacional

2. ORB - El Más Eficiente

  • ✅ Muy rápido (descriptores binarios)
  • ✅ Buen rendimiento en transformaciones moderadas
  • ❌ Sensible a cambios de escala extremos
  • ✅ Ideal para aplicaciones en tiempo real

3. Harris + ORB Descriptors

  • ✅ Excelente localización de esquinas
  • ✅ Keypoints muy estables
  • ❌ No invariante a escala
  • ❌ Puede fallar con transformaciones severas

Conclusiones sobre Matching:¶

Las pruebas revelan que:

  1. SIFT es superior cuando se requiere máxima precisión y robustez
  2. ORB es la mejor opción para aplicaciones con restricciones de tiempo
  3. Harris funciona bien en escenas con esquinas prominentes pero requiere condiciones controladas
  4. La elección del algoritmo debe balancear precisión, velocidad y recursos computacionales disponibles

12. Conclusiones Personales ¶

🎓 Javier Augusto Rebull Saucedo (A01795838)¶

Esta actividad me permitió comprender profundamente cómo los algoritmos de feature matching son fundamentales en visión computacional. Fue particularmente revelador observar cómo SIFT, a pesar de su mayor costo computacional, proporciona resultados superiores en términos de robustez ante transformaciones geométricas. La implementación práctica me hizo valorar el trade-off entre precisión y eficiencia: mientras que SIFT ofrece la mejor calidad de matching, ORB demuestra ser más que suficiente para muchas aplicaciones del mundo real donde la velocidad es crítica.

El uso de principios de Clean Code durante la implementación no solo mejoró la legibilidad del código, sino que facilitó enormemente el proceso de debugging y la expansión del análisis comparativo. Aprendí que en proyectos de ML/CV, una arquitectura de código bien diseñada es tan importante como el algoritmo elegido.

🎓 Juan Carlos Pérez Nava (A01795941)¶

La experimentación con distintos escenarios (rotaciones extremas, oclusiones, cambios de iluminación) me proporcionó una comprensión práctica de las limitaciones y fortalezas de cada algoritmo que va más allá de la teoría. Destaco especialmente cómo Harris Corner Detection, aunque simple en concepto, ofrece una base sólida cuando se combina con descriptores modernos.

Un insight importante fue comprender que la elección del algoritmo no es universal: depende del contexto de aplicación. Para sistemas de navegación de robots que requieren procesamiento en tiempo real, ORB es claramente superior. Sin embargo, para aplicaciones de reconstrucción 3D donde la precisión es primordial, SIFT justifica su mayor costo computacional. Esta actividad reforzó mi habilidad para tomar decisiones de diseño fundamentadas basadas en requisitos específicos del proyecto.

🎓 Luis Gerardo Sánchez Salazar (A01232963)¶

El análisis comparativo cuantitativo mediante métricas de distancia promedio fue particularmente valioso para evaluar objetivamente el rendimiento de cada algoritmo. Ver cómo las distancias de matching varían según las transformaciones aplicadas me ayudó a desarrollar intuición sobre cuándo un algoritmo es apropiado.

La implementación de la clase FeatureMatchingComparator aplicando principios de POO y Clean Code demostró el valor de abstraer funcionalidad común en métodos reutilizables. Esto no solo redujo duplicación de código, sino que hizo el análisis escalable a nuevas imágenes y algoritmos. Aprendí que en proyectos de investigación aplicada, la calidad del código experimental es tan importante como en desarrollo de producción, ya que facilita reproducibilidad y extensión del trabajo.

🎓 Oscar Enrique García García (A01016093)¶

Esta actividad consolidó mi comprensión de los fundamentos matemáticos detrás de cada algoritmo. Entender cómo el Laplacian of Gaussian en SIFT proporciona invarianza a escala, o cómo la función de cornerness en Harris identifica puntos de interés, enriqueció significativamente mi conocimiento teórico.

La visualización de resultados mediante gráficos y tablas comparativas fue esencial para comunicar hallazgos de manera efectiva. Aprendí que en ciencia de datos y ML, la capacidad de presentar resultados de forma clara y visualmente atractiva es tan importante como generar esos resultados. Las herramientas de visualización (matplotlib, seaborn) son fundamentales para el análisis exploratorio y la comunicación de insights a audiencias técnicas y no técnicas.


13. Reflexión Final del Equipo ¶

🚀 Team 13 - Reflexión Colectiva¶

Este proyecto representó una valiosa oportunidad para integrar conocimientos teóricos de visión computacional con implementación práctica y buenas prácticas de ingeniería de software. Como equipo, logramos no solo comparar algoritmos de feature matching, sino establecer un framework metodológico riguroso para evaluación experimental.

Logros Destacados:¶

  1. Implementación Robusta: Desarrollamos código limpio, modular y bien documentado siguiendo principios de Clean Code, lo que facilitó la colaboración y la extensibilidad del proyecto.

  2. Análisis Integral: Realizamos comparaciones exhaustivas en múltiples escenarios realistas, incluyendo transformaciones de escala, rotación, oclusión y cambios de entorno.

  3. Visualización Efectiva: Creamos representaciones gráficas claras que facilitan la interpretación de resultados y la comunicación de insights.

  4. Comprensión Profunda: Cada miembro del equipo desarrolló intuición sobre cuándo aplicar cada algoritmo basándose en requisitos específicos de aplicación.

Aprendizajes Clave:¶

  • No existe un "mejor" algoritmo universal: La elección óptima depende del contexto, requisitos de velocidad, recursos computacionales y nivel de transformaciones esperado.

  • Balance precisión-eficiencia: SIFT ofrece la mayor robustez pero a costa de tiempo de procesamiento; ORB es rápido pero menos robusto ante transformaciones extremas; Harris es simple pero efectivo en casos específicos.

  • Importancia del código de calidad: Aplicar Clean Code en proyectos de ML/CV no es opcional sino esencial para reproducibilidad, mantenibilidad y escalabilidad.

  • Valor de la experimentación: Las observaciones empíricas son fundamentales para validar teoría y desarrollar intuición práctica.

Aplicaciones Potenciales:¶

Los conocimientos adquiridos son directamente aplicables a:

  • Sistemas de navegación autónoma (robots, drones)
  • Realidad aumentada (tracking de objetos)
  • Fotogrametría y reconstrucción 3D
  • Sistemas de reconocimiento visual
  • Aplicaciones de image stitching (panoramas)

Reflexión sobre Trabajo en Equipo:¶

La colaboración fue fundamental para el éxito del proyecto. La división de tareas, comunicación constante y revisión de código entre pares nos permitió detectar errores tempranamente y aprender de las diferentes aproximaciones de cada miembro. Este proyecto reforzó nuestra capacidad para trabajar efectivamente en equipo en proyectos técnicos complejos.

Perspectivas Futuras:¶

Este trabajo sienta las bases para explorar:

  • Algoritmos de feature matching basados en Deep Learning (SuperPoint, D2-Net)
  • Técnicas avanzadas de RANSAC para estimación robusta de transformaciones
  • Implementaciones optimizadas para ejecución en dispositivos embebidos
  • Aplicación de estos conceptos en proyectos capstone de la maestría

📚 Referencias¶

  • Lowe, D. G. (2004). Distinctive Image Features from Scale-Invariant Keypoints. International Journal of Computer Vision.
  • Rublee, E., Rabaud, V., Konolige, K., & Bradski, G. (2011). ORB: An efficient alternative to SIFT or SURF. IEEE International Conference on Computer Vision (ICCV).
  • Harris, C., & Stephens, M. (1988). A Combined Corner and Edge Detector. Alvey Vision Conference.
  • OpenCV Documentation: https://docs.opencv.org/
  • Módulo 3.2 - Extracción de Descriptores (Dr. Ochoa)

Team 13 | Maestría en Inteligencia Artificial Aplicada | Tec de Monterrey | 2025